package middleware.p2p.transaction;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.ejb.Singleton;
import javax.ejb.Startup;
import javax.xml.parsers.DocumentBuilderFactory;

import middleware.p2p.replication.wsclient.ReplicaManager;
import middleware.p2p.replication.wsclient.ReplicaManagerService;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;


/**
 * Session Bean implementation class TransactionRepository
 */
//@Stateful(name="TransactionRepository")
@Singleton
@Startup
public class TransactionRepository implements TransactionRepositoryLocal {	
	private static int progId=0;
	private String coordId;
	private long votingSingleTimeout=10000;
	private	long votingTotalTimeout=30000;		
	private long votingDeltaTimeout=2000;
	private long completitionSingleTimeout=10000;
	private long completitionMaxRetry=3;
	private Map<String, Transaction> transactions;
    /**
     * Default constructor. 
     */
    public TransactionRepository() {    	
    	File confFile=new File("coordinator_config.xml");
		try {
			Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(confFile);
			doc.getDocumentElement().normalize();
			NodeList nodeLst = doc.getElementsByTagName("coordinator");
			if ((nodeLst.getLength()>0)&&(nodeLst.item(0).getNodeType()==Node.ELEMENT_NODE)) {
				Element coordinatorElem=(Element)nodeLst.item(0);
				this.coordId=coordinatorElem.getAttribute("id");
				NodeList timeoutList= coordinatorElem.getElementsByTagName("timeouts");
				if (timeoutList.getLength()>0) {
					NodeList allTimeouts=timeoutList.item(0).getChildNodes();
					for (int k=0;k<allTimeouts.getLength();k++) {
						if (allTimeouts.item(k).getNodeType()==Node.ELEMENT_NODE) {
							Element timeout=(Element)allTimeouts.item(k);
							String timeoutName=timeout.getAttribute("name");
							if (timeoutName.equalsIgnoreCase("voting_single")) {
								votingSingleTimeout=Long.parseLong(timeout.getTextContent());
							} else if (timeoutName.equalsIgnoreCase("voting_total")) {
								votingTotalTimeout=Long.parseLong(timeout.getTextContent());
							} else if (timeoutName.equalsIgnoreCase("voting_delta")) {
								votingDeltaTimeout=Long.parseLong(timeout.getTextContent());
							} else if (timeoutName.equalsIgnoreCase("completition_single")) {
								completitionSingleTimeout=Long.parseLong(timeout.getTextContent());
							} else if (timeoutName.equalsIgnoreCase("completition_max_retry")) {
								completitionMaxRetry=Long.parseLong(timeout.getTextContent());
							}
						}
					}
				}
			}
		} catch (Exception ge) {
			ge.printStackTrace();
			try {
				this.coordId=InetAddress.getLocalHost().getHostAddress();
			} catch (UnknownHostException e) {
				e.printStackTrace();
				this.coordId="UnknownCoordinator";
			}
		} 
		this.transactions=new HashMap<String, Transaction>();		
		Transaction[] openTrans=loadTransactionStatus(true);
		for (int k=0;k<openTrans.length;k++) {
			Transaction trans=openTrans[k];
			URL rmUrl[]=trans.getPartecipants();
			switch (trans.getState()) {
				case GlobalAbort:
				case Prepared:					
					for (int i=0;i<rmUrl.length;i++) {
						ReplicaManagerService serv=new ReplicaManagerService(rmUrl[i]);
						ReplicaManager rm=serv.getReplicaManagerPort();
						rm.doAbortAsync(trans.getId());
					}
					break;
				case GlobalCommit:
					for (int i=0;i<rmUrl.length;i++) {
						ReplicaManagerService serv=new ReplicaManagerService(rmUrl[i]);
						ReplicaManager rm=serv.getReplicaManagerPort();
						rm.doCommitAsync(trans.getId());
					}
					break;
			}
		}
    }
    private String generateTransactionId() {
    	return this.coordId+"_"+String.valueOf(progId++);
    }
	@Override
	public String registerTransaction() {		
		String transId = generateTransactionId();
		Transaction trans=new Transaction(transId);
		this.transactions.put(transId, trans);
		return transId;
	}
	@Override
	public boolean registerJoin(String trans, URL rmWsdl) {
		if (this.transactions.containsKey(trans)) {
			this.transactions.get(trans).registerJoin(rmWsdl);
			return true;
		}
		return false;
	}
	@Override
	public boolean registerCommitVote(String trans,URL rmWsdl, boolean vote) {
		if (this.transactions.containsKey(trans)) {
			return this.transactions.get(trans).registerCommitVote(rmWsdl, vote);
		} else {
			return false;
		}
	}
	@Override
	public boolean registerCommited(String trans, URL rmWsdl) {
		if (this.transactions.containsKey(trans)) {
			return this.transactions.get(trans).registerCommit(rmWsdl);
		} else {
			return false;
		}
	}
	@Override
	public boolean registerAborted(String trans) {
		if (this.transactions.containsKey(trans)) {
			this.transactions.get(trans).setAborted();
			return true;
		} else {
			return false;
		}
	}	
	@Override
	public Transaction getTransaction(String transId) {
		if (this.transactions.containsKey(transId)) {
			return this.transactions.get(transId);
		}
		return null;
	}
	@Override
	public boolean completeTransaction(String transId) {
		this.transactions.remove(transId);
		return true;
	}
	@Override
	public long[] getCoordinatorTimeouts() {
		long allTimeouts[]=new long[5];
		allTimeouts[0]=this.votingSingleTimeout;
		allTimeouts[1]=this.votingTotalTimeout;
		allTimeouts[2]=this.votingDeltaTimeout;
		allTimeouts[3]=this.completitionSingleTimeout;
		allTimeouts[4]=this.completitionMaxRetry;
		return allTimeouts;
	}
	private Transaction[] loadTransactionStatus(boolean coord) {
		String roleFolder=(coord?"coordinator":"partecipant");
    	File logFile=new File(".."+File.separator+"logs"+File.separator+"transaction"+File.separator+roleFolder);    	
    	if (logFile.exists()) {
    		List<Transaction> openTransaction=new LinkedList<Transaction>();
	    	String listFile[]=logFile.list();
	    	for (int k=0;k<listFile.length;k++) {
	    		File currLogFile=new File(listFile[k]);
	    		if (listFile[k].endsWith(".log")) {
	    			//Filter Open Transaction
	    			String transId=currLogFile.getName().replace(".log", "");
	    			Transaction trans=new Transaction(transId);
	    			try {
						BufferedReader inFile=new BufferedReader(new FileReader(logFile));
						String line;
						while ((line=inFile.readLine())!=null) {
							if (line.equalsIgnoreCase("prepare")) {
								trans.setState(TransactionState.Prepared);
							} else if(line.equalsIgnoreCase("global_commit")) {
								trans.setState(TransactionState.GlobalCommit);
							} else if(line.equalsIgnoreCase("global_abort")) {
								trans.setState(TransactionState.GlobalAbort);
							} else if(line.equalsIgnoreCase("complete")) {
								trans.setState(TransactionState.Completed);
							} else if(line.startsWith("LINK")) {
								trans.registerJoin(new URL(line.replace("LINK ", "")));
							}
						}
						openTransaction.add(trans);
					} catch (FileNotFoundException e) {
					} catch (IOException e) {
					}    			
	    		}
	    	}
	    	Transaction retArr[]=new Transaction[openTransaction.size()];
	    	retArr=openTransaction.toArray(retArr);
	    	return retArr;    			
    	} else {
    		return new Transaction[0];
    	}
	}
}